iT邦幫忙

2025 iThome 鐵人賽

DAY 14
1

嘿~ 鐵匠史密斯又來啦
經過前兩天的推導,我們已經讓光線能夠依照方向一步步地走,並且記錄它的座標。
今天要聊的,是光在不同情況下該怎麼處理:

  1. 光走到地圖外
  2. 光還在地圖內但碰到牆

光走道地圖外

地圖 map 的邊界,照理說就是世界盡頭
一旦光線超出邊界,我們就當作它撞到一堵「無形的牆」

// Check if ray is out of bounds
if ((nTestX < 0) || (nTestX >= nMapWidth) || (nTestY < 0) || (nTestY >= nMapHeight))
{
    bHitWall = true; // Out of bounds, we hit the wall
    fDistanceToWall = fDepth; // Set distance to the maximum depth
}

其中 fDepth 就是我們地圖 map 的最大距離,目前是 16 x 16 ,所以玩家的最大可視距離也就是 光可以走的最大距離:

float fDepth{ 16.0f }; // Depth of the raycaster

目前以光行走的最大距離來避免光線走到地圖外圍
日後會有更好的光程檢測方式
https://ithelp.ithome.com.tw/upload/images/20250814/20157653UDPJVYTb72.png

光還在地圖內

如果光線還在地圖內,就要檢查它是不是撞到了世界內的牆(#)。
檢查方式就是用 map 的索引 找對應位置的符號:

else // If ray is still in bounds
{
    if (map[nTestY * nMapWidth + nTestX] == '#') // If we hit a wall
    {
        bHitWall = true; 
    }
}

沒錯,我們地圖 map 是 1-D 的 wstring 陣列,所以查看map 上特定位置 index
index = nTestY * nMapWidth + nTestX
利用 2-D 座標轉換成 1-D wstrin 陣列索引,才可以找出該位置的值: # or
https://ithelp.ithome.com.tw/upload/images/20250814/20157653EhIsSRAEGB.png
這樣的話,只要 bHitwall = true -> 代表光已經碰到牆壁 -> 停止前進並得到距離 fDistanceToWall

今日總結

  • 光線走出地圖外 → 當作撞牆,並將距離設為最大值
  • 光線在地圖內 → 用索引檢查是否撞到牆
  • 核心觀念:不論是地圖外還是地圖內,一旦撞牆就停止光線前進

一旦碰到牆,得到距離 fDistanceToWall 後,一切的光資訊就獲得了
我們就可以依照每道光的資訊,來決定牆在 screen 上的高度與亮度
我們繼續走下去~~~

以下是目前的 code:

#include <iostream>
#include <Windows.h>
#include <string>
using namespace std;

int nMapWidth { 16 };
int nMapHeight { 16 };

float fPlayerX{ 8.0f };
float fPlayerY{ 3.0f };
float fPlayerA{ 0.0f }; // Player angle in radians

float fAOV{ 3.14159f / 4.0f }; // Angle of view in radians
float fDepth{ 16.0f }; // Depth of the raycaster

int main()
{
    const int nScreenWidth { 120 };
    const int nScreenHeight { 40 };
    
    // Create the canvas
    wchar_t* screen{ new wchar_t[nScreenWidth * nScreenHeight] };
    // Create elements in screen
    for (int i = 0; i < nScreenWidth * nScreenHeight; i++)
    {
        if (i < 1200)
            screen[i] = L' ';
        else
            screen[i] = L'%';
    }
    screen[nScreenWidth * nScreenHeight - 1] = '\0'; // null terminator


    // Create console handler (custom screen buffer)
    HANDLE hConsole{ CreateConsoleScreenBuffer(
        GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL) };
    // Set Active Screen Buffer so that console will show this buffer first
    SetConsoleActiveScreenBuffer(hConsole);
    DWORD dwBytesWritten{ 0 };


    // Create world map
    std::wstring map{};

    map += L"################";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"#..............#";
    map += L"################";

    while (true) {
        // Axis going across the screen
        for (int x = 0; x < nScreenWidth; x++)
        {
            // for each column, calculate the projected ray angle into the world space
            float fRayAngle{ (fPlayerA + fAOV / 2.0f) - ((float)x / (float)nScreenWidth) * fAOV };
        
			// Track distance from player to the wall
			float fDistanceToWall{ 0.0f };
			bool bHitWall{ false }; // Whether we hit a wall
            // Unit vector of the ray direction so that we can take one step on that direction to see if we hit a wall
            float fEyeX{ sinf(fRayAngle) };
            float fEyeY{ cosf(fRayAngle) };

			// Unit vector for the ray -> we can make one step on that direction to detect walls
            

            while (!bHitWall && fDistanceToWall < fDepth)
            {
                // If we didn't hit the wall, we can take a step in the ray direction
                fDistanceToWall += 0.1f;
                
                // because wall's boundaries are at integer coordinates(dot index), so we use intger casting to get the coordinates of the wall
                int nTestX{ (int)(fPlayerX + fEyeX * fDistanceToWall) };
                int nTestY{ (int)(fPlayerY + fEyeY * fDistanceToWall) };

				// Check if ray is out of bounds
                if ((nTestX < 0) || (nTestX >= nMapWidth) || (nTestY < 0) || (nTestY >= nMapHeight))
                {
					bHitWall = true; // Out of bounds, we hit the wall
					fDistanceToWall = fDepth; // Set distance to the maximum depth
                }
				else // If ray is still in bounds
                {
                    if (map[nTestY * nMapWidth + nTestX] == '#') // If we hit a wall
                    {
                        bHitWall = true; 
					}
                }
            }

        }
        // Write cavas into console to show screen 
        WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten);
    }
    return 0;

}


上一篇
Day 13 | Ray Casting : 光如何前進? Part 2
下一篇
Day 15 | Ray Casting : 距離決定牆壁的高矮 - Part 1
系列文
用 C++ 實作簡易第一人稱視角遊戲:從入門到理解 Ray Casting30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言